﻿
namespace Anycmd.Host.AC.MemorySets.Impl
{
    using Anycmd.AC;
    using Bus;
    using Exceptions;
    using Extensions;
    using Host;
    using Host.AC.Messages;
    using Host.Impl;
    using Repositories;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using ValueObjects;

    public sealed class SSDSetSet : ISSDSetSet
    {
        public static readonly ISSDSetSet Empty = new SSDSetSet(EmptyAppHost.SingleInstance);

        private readonly Dictionary<Guid, SSDSetState> _ssdSetDic = new Dictionary<Guid, SSDSetState>();
        private readonly Dictionary<SSDSetState, List<SSDRoleState>> _ssdRoleBySet = new Dictionary<SSDSetState, List<SSDRoleState>>();
        private readonly Dictionary<Guid, SSDRoleState> _ssdRoleByID = new Dictionary<Guid, SSDRoleState>();
        private bool _initialized = false;

        private readonly Guid _id = Guid.NewGuid();
        private readonly IAppHost host;
        public Guid Id
        {
            get { return _id; }
        }

        public SSDSetSet(IAppHost host)
        {
            if (host == null)
            {
                throw new ArgumentNullException("host");
            }
            this.host = host;
            new MessageHandler(this).Register();
        }

        public bool TryGetSSDSet(Guid ssdSetID, out SSDSetState ssdSet)
        {
            if (!_initialized)
            {
                Init();
            }
            return _ssdSetDic.TryGetValue(ssdSetID, out ssdSet);
        }

        public IReadOnlyCollection<SSDRoleState> GetSSDRoles(SSDSetState ssdSet)
        {
            if (!_initialized)
            {
                Init();
            }
            if (ssdSet == null)
            {
                throw new ArgumentNullException("ssdSet");
            }
            if (!_ssdRoleBySet.ContainsKey(ssdSet))
            {
                return new List<SSDRoleState>();
            }
            return _ssdRoleBySet[ssdSet];
        }

        public IReadOnlyCollection<SSDRoleState> GetSSDRoles()
        {
            if (!_initialized)
            {
                Init();
            }
            var result = new List<SSDRoleState>();
            foreach (var item in _ssdRoleByID)
            {
                result.Add(item.Value);
            }

            return result;
        }

        public bool CheckRoles(IEnumerable<RoleState> roles, out string msg)
        {
            if (!_initialized)
            {
                Init();
            }
            if (roles == null)
            {
                new ArgumentNullException("roles");
            }
            foreach (var ssdSet in _ssdSetDic.Values)
            {
                var ssdRoles = _ssdRoleBySet[ssdSet];
                var ssdCard = ssdSet.SSDCard;
                if (roles.Count(a => ssdRoles.Any(b => b.RoleID == a.Id)) > ssdCard)
                {
                    msg = "违反了" + ssdSet.Name + "约束";
                    return false;
                }
            }
            msg = string.Empty;
            return true;
        }

        public IEnumerator<SSDSetState> GetEnumerator()
        {
            if (!_initialized)
            {
                Init();
            }
            return _ssdSetDic.Values.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            if (!_initialized)
            {
                Init();
            }
            return _ssdSetDic.Values.GetEnumerator();
        }

        private void Init()
        {
            if (!_initialized)
            {
                lock (this)
                {
                    if (!_initialized)
                    {
                        _ssdSetDic.Clear();
                        _ssdRoleBySet.Clear();
                        _ssdRoleByID.Clear();
                        var stateReder = host.GetRequiredService<IOriginalHostStateReader>();
                        var ssdSets = stateReder.GetAllSSDSets();
                        foreach (var ssdSet in ssdSets)
                        {
                            if (!(ssdSet is SSDSetBase))
                            {
                                throw new CoreException(ssdSet.GetType().Name + "必须继承" + typeof(SSDSetBase).Name);
                            }
                            if (!_ssdSetDic.ContainsKey(ssdSet.Id))
                            {
                                _ssdSetDic.Add(ssdSet.Id, SSDSetState.Create(ssdSet));
                            }
                        }
                        var ssdRoles = stateReder.GetAllSSDRoles();
                        foreach (var ssdRole in ssdRoles)
                        {
                            if (!(ssdRole is SSDRoleBase))
                            {
                                throw new CoreException(ssdRole.GetType().Name + "必须继承" + typeof(SSDRoleBase).Name);
                            }
                            SSDSetState ssdSetState;
                            if (_ssdSetDic.TryGetValue(ssdRole.SSDSetID, out ssdSetState))
                            {
                                var state = SSDRoleState.Create(ssdRole);
                                if (!_ssdRoleByID.ContainsKey(ssdRole.Id))
                                {
                                    _ssdRoleByID.Add(ssdRole.Id, state);
                                }
                                if (!_ssdRoleBySet.ContainsKey(ssdSetState))
                                {
                                    _ssdRoleBySet.Add(ssdSetState, new List<SSDRoleState>());
                                }
                                _ssdRoleBySet[ssdSetState].Add(state);
                            }
                            else
                            {
                                // TODO:删除非法的记录
                            }
                        }
                        _initialized = true;
                    }
                }
            }
        }

        #region MessageHandler
        private class MessageHandler :
            IHandler<AddSSDSetCommand>,
            IHandler<SSDSetAddedEvent>,
            IHandler<SSDSetUpdatedEvent>,
            IHandler<UpdateSSDSetCommand>,
            IHandler<RemoveSSDSetCommand>,
            IHandler<SSDSetRemovedEvent>,
            IHandler<AddSSDRoleCommand>,
            IHandler<RemoveSSDRoleCommand>,
            IHandler<SSDRoleAddedEvent>,
            IHandler<SSDRoleRemovedEvent>
        {
            private readonly SSDSetSet set;

            public MessageHandler(SSDSetSet set)
            {
                this.set = set;
            }

            public void Register()
            {
                var messageDispatcher = set.host.MessageDispatcher;
                if (messageDispatcher == null)
                {
                    throw new ArgumentNullException("messageDispatcher has not be set of host:{0}".Fmt(set.host.Name));
                }
                messageDispatcher.Register((IHandler<AddSSDSetCommand>)this);
                messageDispatcher.Register((IHandler<SSDSetAddedEvent>)this);
                messageDispatcher.Register((IHandler<UpdateSSDSetCommand>)this);
                messageDispatcher.Register((IHandler<SSDSetUpdatedEvent>)this);
                messageDispatcher.Register((IHandler<RemoveSSDSetCommand>)this);
                messageDispatcher.Register((IHandler<SSDSetRemovedEvent>)this);
                messageDispatcher.Register((IHandler<AddSSDRoleCommand>)this);
                messageDispatcher.Register((IHandler<RemoveSSDRoleCommand>)this);
                messageDispatcher.Register((IHandler<SSDRoleAddedEvent>)this);
                messageDispatcher.Register((IHandler<SSDRoleRemovedEvent>)this);
            }

            public void Handle(AddSSDSetCommand message)
            {
                this.Handle(message.Input, true);
            }

            public void Handle(SSDSetAddedEvent message)
            {
                if (message.GetType() == typeof(PrivateSSDSetAddedEvent))
                {
                    return;
                }
                this.Handle(message.Input, false);
            }

            private void Handle(ISSDSetCreateInput input, bool isCommand)
            {
                var host = set.host;
                var _ssdSetDic = set._ssdSetDic;
                var ssdSetRepository = host.GetRequiredService<IRepository<SSDSet>>();
                if (!input.Id.HasValue)
                {
                    throw new ValidationException("标识是必须的");
                }
                if (host.SSDSetSet.Any(a => a.Name.Equals(input.Name, StringComparison.OrdinalIgnoreCase)))
                {
                    throw new ValidationException("重复的静态责任分离角色集名称");
                }

                var entity = SSDSet.Create(input);

                lock (this)
                {
                    SSDSetState ssdSet;
                    if (host.SSDSetSet.TryGetSSDSet(entity.Id, out ssdSet))
                    {
                        throw new CoreException("意外的重复标识");
                    }
                    if (!_ssdSetDic.ContainsKey(entity.Id))
                    {
                        _ssdSetDic.Add(entity.Id, SSDSetState.Create(entity));
                    }
                    if (isCommand)
                    {
                        try
                        {
                            ssdSetRepository.Add(entity);
                            ssdSetRepository.Context.Commit();
                        }
                        catch
                        {
                            if (_ssdSetDic.ContainsKey(entity.Id))
                            {
                                _ssdSetDic.Remove(entity.Id);
                            }
                            ssdSetRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateSSDSetAddedEvent(entity, input));
                }
            }

            private class PrivateSSDSetAddedEvent : SSDSetAddedEvent
            {
                public PrivateSSDSetAddedEvent(SSDSetBase source, ISSDSetCreateInput input)
                    : base(source, input)
                {
                }
            }

            public void Handle(UpdateSSDSetCommand message)
            {
                this.Handle(message.Input, true);
            }

            public void Handle(SSDSetUpdatedEvent message)
            {
                if (message.GetType() == typeof(PrivateSSDSetUpdatedEvent))
                {
                    return;
                }
                this.Handle(message.Input, false);
            }

            private void Handle(ISSDSetUpdateInput input, bool isCommand)
            {
                var host = set.host;
                var _ssdSetDic = set._ssdSetDic;
                var ssdSetRepository = host.GetRequiredService<IRepository<SSDSet>>();
                SSDSetState bkState;
                if (!host.SSDSetSet.TryGetSSDSet(input.Id, out bkState))
                {
                    throw new NotExistException();
                }
                SSDSet entity;
                bool stateChanged = false;
                lock (bkState)
                {
                    SSDSetState oldState;
                    if (!host.SSDSetSet.TryGetSSDSet(input.Id, out oldState))
                    {
                        throw new NotExistException();
                    }
                    if (host.SSDSetSet.Any(a => a.Name.Equals(input.Name, StringComparison.OrdinalIgnoreCase) && a.Id != input.Id))
                    {
                        throw new ValidationException("重复的静态责任分离角色组名");
                    }
                    entity = ssdSetRepository.GetByKey(input.Id);
                    if (entity == null)
                    {
                        throw new NotExistException();
                    }

                    entity.Update(input);

                    var newState = SSDSetState.Create(entity);
                    stateChanged = newState != bkState;
                    if (stateChanged)
                    {
                        Update(newState);
                    }
                    if (isCommand)
                    {
                        try
                        {
                            ssdSetRepository.Update(entity);
                            ssdSetRepository.Context.Commit();
                        }
                        catch
                        {
                            if (stateChanged)
                            {
                                Update(bkState);
                            }
                            ssdSetRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand && stateChanged)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateSSDSetUpdatedEvent(entity, input));
                }
            }

            private void Update(SSDSetState state)
            {
                var host = set.host;
                var _ssdSetDic = set._ssdSetDic;
                _ssdSetDic[state.Id] = state;
            }

            private class PrivateSSDSetUpdatedEvent : SSDSetUpdatedEvent
            {
                public PrivateSSDSetUpdatedEvent(SSDSetBase source, ISSDSetUpdateInput input)
                    : base(source, input)
                {

                }
            }

            public void Handle(RemoveSSDSetCommand message)
            {
                this.Handle(message.EntityID, true);
            }

            public void Handle(SSDSetRemovedEvent message)
            {
                if (message.GetType() == typeof(PrivateSSDSetRemovedEvent))
                {
                    return;
                }
                this.Handle(message.Source.Id, false);
            }

            private void Handle(Guid ssdSetID, bool isCommand)
            {
                var host = set.host;
                var _ssdSetDic = set._ssdSetDic;
                var ssdSetRepository = host.GetRequiredService<IRepository<SSDSet>>();
                SSDSetState bkState;
                if (!host.SSDSetSet.TryGetSSDSet(ssdSetID, out bkState))
                {
                    return;
                }
                SSDSet entity;
                lock (bkState)
                {
                    SSDSetState state;
                    if (!host.SSDSetSet.TryGetSSDSet(ssdSetID, out state))
                    {
                        return;
                    }
                    entity = ssdSetRepository.GetByKey(ssdSetID);
                    if (entity == null)
                    {
                        return;
                    }
                    if (_ssdSetDic.ContainsKey(bkState.Id))
                    {
                        _ssdSetDic.Remove(bkState.Id);
                    }
                    if (isCommand)
                    {
                        try
                        {
                            ssdSetRepository.Remove(entity);
                            ssdSetRepository.Context.Commit();
                        }
                        catch
                        {
                            if (!_ssdSetDic.ContainsKey(entity.Id))
                            {
                                _ssdSetDic.Add(bkState.Id, bkState);
                            }
                            ssdSetRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateSSDSetRemovedEvent(entity));
                }
            }

            private class PrivateSSDSetRemovedEvent : SSDSetRemovedEvent
            {
                public PrivateSSDSetRemovedEvent(SSDSetBase source)
                    : base(source)
                {
                }
            }

            public void Handle(AddSSDRoleCommand message)
            {
                this.Handle(message.Input, true);
            }

            public void Handle(SSDRoleAddedEvent message)
            {
                if (message.GetType() == typeof(PrivateSSDRoleAddedEvent))
                {
                    return;
                }
                this.Handle(message.Input, false);
            }

            private void Handle(ISSDRoleCreateInput input, bool isCommand)
            {
                var host = set.host;
                var _ssdRoleBySet = set._ssdRoleBySet;
                var _ssdRoleByID = set._ssdRoleByID;
                var ssdRoleRepository = host.GetRequiredService<IRepository<SSDRole>>();
                if (!input.Id.HasValue)
                {
                    throw new ValidationException("标识是必须的");
                }
                if (_ssdRoleByID.Any(a => a.Key == input.Id.Value || (a.Value.RoleID == input.RoleID && a.Value.SSDSetID == input.SSDSetID)))
                {
                    throw new ValidationException("重复的记录");
                }
                SSDSetState ssdSet;
                if (!host.SSDSetSet.TryGetSSDSet(input.SSDSetID, out ssdSet))
                {
                    throw new ValidationException("意外的静态责任分离角色集标识" + input.SSDSetID);
                }

                var entity = SSDRole.Create(input);

                lock (this)
                {
                    if (_ssdRoleByID.Any(a => a.Key == input.Id.Value || (a.Value.RoleID == input.RoleID && a.Value.SSDSetID == input.SSDSetID)))
                    {
                        throw new ValidationException("重复的记录");
                    }
                    if (!host.SSDSetSet.TryGetSSDSet(input.SSDSetID, out ssdSet))
                    {
                        throw new ValidationException("意外的静态责任分离角色集标识" + input.SSDSetID);
                    }
                    var state = SSDRoleState.Create(entity);
                    if (!_ssdRoleByID.ContainsKey(entity.Id))
                    {
                        _ssdRoleByID.Add(entity.Id, state);
                    }
                    if (!_ssdRoleBySet.ContainsKey(ssdSet))
                    {
                        _ssdRoleBySet.Add(ssdSet, new List<SSDRoleState>());
                    }
                    _ssdRoleBySet[ssdSet].Add(state);
                    if (isCommand)
                    {
                        try
                        {
                            ssdRoleRepository.Add(entity);
                            ssdRoleRepository.Context.Commit();
                        }
                        catch
                        {
                            if (_ssdRoleByID.ContainsKey(entity.Id))
                            {
                                _ssdRoleByID.Remove(entity.Id);
                            }
                            _ssdRoleBySet[ssdSet].Remove(state);
                            ssdRoleRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateSSDRoleAddedEvent(entity, input));
                }
            }

            private class PrivateSSDRoleAddedEvent : SSDRoleAddedEvent
            {
                public PrivateSSDRoleAddedEvent(SSDRoleBase source, ISSDRoleCreateInput input)
                    : base(source, input)
                {

                }
            }

            public void Handle(RemoveSSDRoleCommand message)
            {
                this.HandleSSDRole(message.EntityID, true);
            }

            public void Handle(SSDRoleRemovedEvent message)
            {
                if (message.GetType() == typeof(PrivateSSDRoleRemovedEvent))
                {
                    return;
                }
                this.HandleSSDRole(message.Source.Id, false);
            }

            private void HandleSSDRole(Guid ssdRoleID, bool isCommand)
            {
                var host = set.host;
                var _ssdSetDic = set._ssdSetDic;
                var _ssdRoleBySet = set._ssdRoleBySet;
                var _ssdRoleByID = set._ssdRoleByID;
                var ssdRoleRepository = host.GetRequiredService<IRepository<SSDRole>>();
                SSDRoleState bkState;
                if (!_ssdRoleByID.TryGetValue(ssdRoleID, out bkState))
                {
                    return;
                }
                SSDRole entity;
                lock (bkState)
                {
                    SSDRoleState state;
                    if (!_ssdRoleByID.TryGetValue(ssdRoleID, out state))
                    {
                        return;
                    }
                    entity = ssdRoleRepository.GetByKey(ssdRoleID);
                    if (entity == null)
                    {
                        return;
                    }
                    if (_ssdRoleByID.ContainsKey(bkState.Id))
                    {
                        _ssdRoleByID.Remove(bkState.Id);
                    }
                    SSDSetState ssdSet;
                    if (_ssdSetDic.TryGetValue(entity.SSDSetID, out ssdSet))
                    {
                        _ssdRoleBySet[ssdSet].Remove(bkState);
                    }
                    if (isCommand)
                    {
                        try
                        {
                            ssdRoleRepository.Remove(entity);
                            ssdRoleRepository.Context.Commit();
                        }
                        catch
                        {
                            if (!_ssdRoleByID.ContainsKey(entity.Id))
                            {
                                _ssdRoleByID.Add(bkState.Id, bkState);
                            }
                            _ssdRoleBySet[ssdSet].Add(bkState);
                            ssdRoleRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateSSDRoleRemovedEvent(entity));
                }
            }

            private class PrivateSSDRoleRemovedEvent : SSDRoleRemovedEvent
            {
                public PrivateSSDRoleRemovedEvent(SSDRoleBase source)
                    : base(source)
                {
                }
            }
        }
        #endregion
    }
}
